package com.atguigu.tingshu.common.login;

import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * projectName: tingshu-parent
 *
 * @author: 比丢比丢
 * time: 2024/8/6 10:47 周二
 * description: 登录校验切面类
 */
@Slf4j
@Aspect
@Component
public class GuiGuLoginAspect {

    @Autowired
    private RedisTemplate redisTemplate;

    //环绕通知(执行com.atguigu.tingshu包下所有api 类的方法前检查这些方法是否标注了 guiGuLogin 注解)
    @Around("execution(* com.atguigu.tingshu.*.api.*.*(..)) && @annotation(guiGuLogin)")
    public Object doBasicProfiling(ProceedingJoinPoint joinPoint,GuiGuLogin guiGuLogin) throws Throwable {

        //1. 前置逻辑
        log.info("[认证注解切面]这里是前置逻辑...");
        //1.1 获取请求对象,获取请求头名称为token的用户令牌
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes)requestAttributes;
        HttpServletRequest request = sra.getRequest();
        String token = request.getHeader("token");
        //1.2 将token作为key查询redis中的用户信息
        //1.2.1 构建登录成功存入redis用户令牌key查询 形式:user:login:token值
        String loginKen = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
        //1.2.2 获取已经存在redis中的用户信息UserInfoVo
        UserInfoVo userInfoVo = (UserInfoVo) redisTemplate.opsForValue().get(loginKen);
        //1.3 如果认证注解对象属性required=true且用户信息为空抛出异常:状态码=208,引导用户登录
        if (guiGuLogin.required() && userInfoVo == null) {
            throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
        }
        //1.4 如果用户信息有值,将用户id存入ThreadLocal中
        if (userInfoVo != null) {
            AuthContextHolder.setUserId(userInfoVo.getId());
        }
        //2. 执行目标方法(Controller-->Service-->Mapper)
        Object retVal = joinPoint.proceed();
        //3. 后置逻辑
        log.info("[认证注解切面]这里是后置逻辑...");
        //3.1 手动清理ThreadLocal避免出现使用不当导致内存泄漏,不断内存泄露会导致内存溢出
        AuthContextHolder.removeUserId();
        return retVal;
    }
}
